/*
* Copyright 2010 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/

package org.etherframework.wire.wirings
{
	import org.etherframework.behaviour.rules.events.FeedbackEvent;
	import org.etherframework.core.Access;
	import org.etherframework.core.DomainAccess;
	import org.etherframework.core.IBridge;
	import org.etherframework.core.events.InteractionEvent;
	import org.etherframework.reflection.Accessor;
	import org.etherframework.reflection.Metadata;
	import org.etherframework.reflection.MetadataMember;
	import org.etherframework.reflection.Method;
	import org.etherframework.reflection.Variable;
	import org.etherframework.wire.WiringData;
	import org.etherframework.wire.WiringUtil;
	
	import flash.events.Event;
	import flash.utils.Dictionary;
	
	import mx.utils.UIDUtil;
	
	public final class FeedbackListener implements IWiring, IInjection
	{
		[Inspectable(defaultValue="local", enumeration="local,hierarchical,@hierarchical,hierarchical@,global")]
		public var access:String = "local";
		
		[Inspectable(defaultValue="single", enumeration="single,multiple")]
		public var domain:String = "single";
		
		public var feedback:String;
		
		public var strict:Boolean;
		
		public var member:Object;
		
		public var parameters:String;
		
		public var kind:String;
		
		public var prevent:Boolean = false;
		
		private var dictionary:Dictionary;
		
		private var properties:Dictionary;
		
		private var bridge:IBridge;
		
		public function wire(name:String, target:Object, item:Object, data:WiringData):Boolean
		{
			if (!(item is MetadataMember))
				return false;
			
			var member:MetadataMember = item as MetadataMember;
			var result:Boolean = false;
			var metadatas:Array;
			var metadatasLen:int;
			var metadata:Metadata;
			var properties:Dictionary;
			var feedbackType:String;
			var feedbackValue:String;
			var resolvedName:String;
			
			metadatas = member.metadata;
			metadatasLen = metadatas.length;
			this.member = (member.uri) ? 
				new QName(new Namespace(member.uri), member.name) : member.name;
			bridge = data.bridge;
			
			for (var i:int = 0; i < metadatasLen; ++i)
			{
				metadata = metadatas[i];
				
				if (metadata.name == name)
				{
					properties = WiringUtil.getProperties(metadata.properties);
					access = Access.getAccess(properties["access"]);
					domain = DomainAccess.getAccess(properties["domain"]);
					feedback = properties["feedback"];
					strict = (properties["strict"] == "true");
					prevent = (properties["prevent"] == "true");
					parameters = properties["parameters"];
					kind = properties["kind"];
					
					if (feedback)
					{
						resolvedName = WiringUtil.resolveName(data.resolvers, feedback, data.bridge.domain);
						feedbackValue = ((resolvedName.indexOf(".") > 0) || !kind) ? resolvedName : resolvedName+"."+kind; 
						feedbackType = (strict) ? InteractionEvent.FEEDBACK + UIDUtil.getUID(target) + feedbackValue :
												  InteractionEvent.FEEDBACK + feedbackValue; 
						
						bridge.listen(feedbackType, feedbackHandler, access, domain, false);
						
						this.properties = properties;
						dictionary = new Dictionary(true);
						dictionary[target] = item;
					}
				}
			}
			
			return Boolean(feedback);
		}
		
		public function unwire(target:Object):Boolean
		{
			dictionary = null;
			
			return true;
		}
		
		public function inject(name:String, target:Object, data:WiringData):Boolean
		{
			try {
				if (feedback && ((target[member] is Function) || !target[member]))
				{
					bridge = data.bridge;
					var resolvedName:String = WiringUtil.resolveName(data.resolvers, feedback, data.bridge.domain);
					var feedbackValue:String = ((resolvedName.indexOf(".") > 0) || !kind) ? 
												resolvedName : resolvedName+"."+kind;
					var feedbackType:String = (strict) ? InteractionEvent.FEEDBACK + UIDUtil.getUID(target) + feedbackValue : 
														 InteractionEvent.FEEDBACK + feedbackValue;
					access = Access.getAccess(access);
					domain = DomainAccess.getAccess(domain);
					
					bridge.listen(feedbackType, feedbackHandler, access, domain, false);
					
					dictionary = new Dictionary(true);
					dictionary[target] = true;
					
					return true;
				}
			} catch (e:*) {}
			
			return false;
		}
		
		private function feedbackHandler(event:Event):void
		{
			var item:*;
			var target:Object;
			for (target in dictionary)
				item = dictionary[target];
			
			if (!target)
				return;
			
			var evt:FeedbackEvent = event["data"];
			var params:Array = parameters ? getParameters(evt, parameters) : [];
			
			if (prevent)
			{
				event.preventDefault();
			}
			
			try {
				if (item is Boolean)
				{
					if (target[member] is Function)
					{
						target[member].apply(null, params);
					}
					else if (params.length)
					{
						target[member] = (params.length > 1) ? params : params[0];
					}
				}
				else if (item is Method)
				{
					if (Method(item).parameters.length)
					{
						if (Method(item).parameters.length == params.length)
						{
							target[member].apply(null, params);
						}
					}
					else
					{
						target[member].apply(null, []);
					}
					
				}
				else if ((item is Accessor) || (item is Variable))
				{
					if (params.length)
					{
						target[member] = (params.length > 1) ? params : params[0];
					}
				}
			} catch (e:Error) {
				
			}
		}
		
		private function getParameters(obj:Object, params:String):Array
		{
			var result:Array = [];
			
			if (params && (params.indexOf("@") == 0))
			{
				params = (params.indexOf(" ") >= 0) ? params.replace(" ", "") : params;   
				
				var args:Array = params.split(",");
				var argsLen:int = args.length;
				var props:Array;
				var propsLen:int;
				var prop:String;
				var objValue:*;
				var i:int, j:int;
				
				for (i = 0; i < argsLen; ++i)
				{
					props = args[i].split(".");
					
					if (props[0] != "@")
						return result;
					
					if (props.length == 1)
					{
						result.push(obj);
						continue;
					}
					else
					{
						props.shift();
					}
					
					objValue = obj;
					
					for (j = 0; j < props.length; ++j)
					{
						prop = props[j];
						
						if (!(prop in objValue))
							return result;
						
						objValue = objValue[prop];
					}
					
					result.push(objValue);
				}
			}
			
			return result;
		}
		
	}
}